﻿/*
 * 
 * Thanks to Silverlight Games 101 for the blitting code.
 * The code has been modified somewhat with a few added methods:
 *  - Blit(this WriteableBitmap bmp, IRenderable source, int x, int y)
 *  - Blit(this WriteableBitmap bmp, ISprite source, BlendMode blendMode)
 * In addition I have changed the WriteableBitmap input argument to use an IRenderable instead.
 * Source url: http://blogs.silverarcade.com/silverlight-games-101/15/silverlight-blitting-and-blending-with-silverlights-writeablebitmap/
 */
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Media.Imaging;
using SilverTile;
using SilverTile.Sprites;

namespace SilverArcade.Graphics
{
	public enum BlendMode
	{
		AlphaBlend,
		Additive,
		Subtractive,
		None
	}

	public static class WriteableBitmapExtensions
	{

		static int blend(int a, int b, int c)
		{
			return ((((((a * b) * 0x8081) >> 23) * c) * 0x8081) >> 23);
		}

		static int blend(byte a, byte b)
		{
			return (((a * b) * 0x8081) >> 23);
		}

		static int blend(int a, int b)
		{
			return (((a * b) * 0x8081) >> 23);
		}

		public static void Fade(this WriteableBitmap bmp, int fadeAmount, bool fadeAlpha)
		{
			int len = bmp.Pixels.Length;
			for (int i = 0; i < len; i++)
			{
				int pixel = bmp.Pixels[i];
				int a = (pixel >> 24) & 0xff;
				int r = (pixel >> 16) & 0xff;
				int g = (pixel >> 8) & 0xff;
				int b = (pixel) & 0xff;
				if (fadeAlpha) a = Math.Max(0, a - 10);
				r = Math.Max(0, r - 10);
				g = Math.Max(0, g - 10);
				b = Math.Max(0, b - 10);
				bmp.Pixels[i] = (int)((a << 24) | (r << 16) | (g << 8) | b);
			}
		}

        public static void Blit(this WriteableBitmap bmp, Point destPosition, IRenderable source, Rect sourceRect, Color color, BlendMode blendMode)
		{
			Rect destRect = new Rect(destPosition, new Size(sourceRect.Width, sourceRect.Height));
			Blit(bmp, destRect, source, sourceRect, color, blendMode);
		}

        public static void Blit(this WriteableBitmap bmp, Rect destRect, IRenderable source, Rect sourceRect, BlendMode blendMode)
		{
			Blit(bmp, destRect, source, sourceRect, Colors.White, blendMode);
		}

        public static void Blit(this WriteableBitmap bmp, Rect destRect, IRenderable source, Rect sourceRect)
		{
			Blit(bmp, destRect, source, sourceRect, Colors.White, BlendMode.AlphaBlend);
		}

        public static void Blit(this WriteableBitmap bmp, IRenderable source, int x, int y)
        {
            Rect destRect = new Rect(new Point(x,y), new Point(source.Width, source.Height));
            Rect sourceRect = new Rect(new Point(0,0), new Point(source.Width, source.Height));
            Blit(bmp, destRect, source, sourceRect, Colors.White, BlendMode.AlphaBlend);
        }

        public static void Blit(this WriteableBitmap bmp, ISprite source, BlendMode blendMode)
        {
            Rect destRect = new Rect(new Point(source.X - source.OriginX,source.Y - source.OriginY), new Size(source.Width, source.Height));
            Rect sourceRect = new Rect(new Point(0,0), new Size(source.Width, source.Height));
            Blit(bmp, destRect, source, sourceRect, Colors.White, blendMode);
        }


		public static void Blit(this WriteableBitmap bmp, Rect destRect, IRenderable source, Rect sourceRect, Color color, BlendMode blendMode)
		{
			if (color.A == 0) return;
			int dw = (int)destRect.Width;
			int dh = (int)destRect.Height;
			int dpw = bmp.PixelWidth;
			int dph = bmp.PixelHeight;
			Rect intersect = new Rect(0, 0, dpw, dph);
			intersect.Intersect(destRect);
			if (intersect.IsEmpty)
			{
				return;
			}
			int sourceWidth = source.Width;

			int[] sourcePixels = source.Pixels;
			int[] destPixels = bmp.Pixels;
			int sourceLength = sourcePixels.Length;
			int destLength = destPixels.Length;
			int sourceIdx = -1;
			int px = (int)destRect.X;
			int py = (int)destRect.Y;
			int right = px + dw;
			int bottom = py + dh;
			int x;
			int y;
			int idx;
			double ii;
			double jj;
			int sr = 0;
			int sg = 0;
			int sb = 0;
			int dr, dg, db;
			int sourcePixel;
			int sa = 0;
			int da;
			int ca = color.A;
			int cr = color.R;
			int cg = color.G;
			int cb = color.B;
			bool tinted = color != Colors.White;
			double sdx = sourceRect.Width / destRect.Width;
			double sdy = sourceRect.Height / destRect.Height;
			int sourceStartX = (int)sourceRect.X;
			int sourceStartY = (int)sourceRect.Y;
			int lastii, lastjj;
			lastii = -1;
			lastjj = -1;
			jj = sourceStartY;
			y = py;
			for (int j = 0; j < dh; j++)
			{
				if (y >= 0 && y < dph)
				{
					ii = sourceStartX;
					idx = px + y * dpw;
					x = px;
					sourcePixel = sourcePixels[0];

					for (int i = 0; i < dw; i++)
					{
						if (x >= 0 && x < dpw)
						{
							if ((int)ii != lastii || (int)jj != lastjj)
							{
								sourceIdx = (int)ii + (int)jj * sourceWidth;
								if (sourceIdx >= 0 && sourceIdx < sourceLength)
								{
									sourcePixel = sourcePixels[sourceIdx];
									sa = (byte)((sourcePixel >> 24) & 0xff);
									sr = (byte)((sourcePixel >> 16) & 0xff);
									sg = (byte)((sourcePixel >> 8) & 0xff);
									sb = (byte)((sourcePixel) & 0xff);
									if (tinted && sa != 0)
									{
										sa = (byte)blend(sa, ca);
										sr = blend(sr, cr, ca);
										sg = blend(sg, cg, ca);
										sb = blend(sb, cb, ca);
										sourcePixel = (sa << 24) | (sr << 16) | (sg << 8) | sb;
									}
								}
								else
								{
									sa = 0;
								}
							}
							if (blendMode == BlendMode.None)
							{
								destPixels[idx] = sourcePixel;
							}
							else if (sa > 0)
							{
								int destPixel = destPixels[idx];
								da = ((destPixel >> 24) & 0xff);
								if ((sa == 255 || da == 0) && 
									blendMode != BlendMode.Additive && 
									blendMode != BlendMode.Subtractive 
									)
								{
									destPixels[idx] = sourcePixel;
								}
								else
								{
									dr = ((destPixel >> 16) & 0xff);
									dg = ((destPixel >> 8) & 0xff);
									db = ((destPixel) & 0xff);
									if (blendMode == BlendMode.AlphaBlend)
									{
										destPixel = ((sa + blend(da, (255 - sa))) << 24) |
											((blend(sr, sa) + blend(dr, (255 - sa))) << 16) |
											((blend(sg, sa) + blend(dg, (255 - sa))) << 8) |
											(blend(sb, sa) + blend(db, (byte)(255 - sa)));
									}
									else if (blendMode == BlendMode.Additive)
									{
										int a = Math.Min(255, sa + da);
										destPixel = ((Math.Min(255, sa + da)) << 24) |
											 ((Math.Min(a, sr + dr)) << 16) |
											 ((Math.Min(a, sg + dg)) << 8) |
											 (Math.Min(a, sb + db));

									}
									else if (blendMode == BlendMode.Subtractive)
									{
										int a = da;
										destPixel = (a << 24) |
											 ((Math.Max(0, Math.Min(a, dr - sr))) << 16) |
											 ((Math.Max(0, Math.Min(a, dg - sg))) << 8) |
											 ((Math.Max(0, Math.Min(a, db - sb))));

									}
									destPixels[idx] = destPixel;
								}
							}
						}
						x++;
						idx++;
						ii += sdx;
					}
				}
				jj += sdy;
				y++;
			}
		}
	}
}
